Skip to content

Durable evaluations

Evaluate saved application answers against explicit behavior criteria, with a shared judge budget and reusable results. Each criterion produces a verdict tied to a frozen answer, conversation, rubric revision, judge configuration, and request. Reports and breakdowns read those saved verdicts and make no judge calls.

This path supports generic rubrics and built-in grounding and faithfulness. Existing alignmenter run scorers remain available with their legacy accounting and result contracts. Their historical calls are not retroactively included in the new budget. The budget's reported scope is durable_evaluations on one capture run.

Capture, evaluate, inspect

alignmenter capture --dataset conversations.jsonl --persona persona.yaml --out reports

alignmenter evaluate reports/<run-directory> \
  --spec rubrics.yaml --judge-factory my_eval.judge:make_judge --max-judge-calls 20

# Repeat the same command after interruption; omit budget options to reuse its limits.
alignmenter evaluate reports/<run-directory> \
  --spec rubrics.yaml --judge-factory my_eval.judge:make_judge

# These commands only read saved data; they do not load the judge factory.
alignmenter evaluation-status reports/<run-directory>
alignmenter evaluation-status reports/<run-directory> --json
alignmenter evaluation-status reports/<run-directory> --details > evaluation.json

evaluate returns an evaluation UUID. Use evaluation-status --evaluation-id UUID to inspect an older evaluation; the default is the latest saved evaluation. Detailed JSON includes the frozen inputs, exact raw replies, result reasons, and evidence citations. It is a review export, not an importable checkpoint or a complete portable run archive.

The evaluation command exits 0 for a passing reviewed rubric, 2 for a violated criterion, and 3 for an inconclusive evaluation. Configuration/CLI errors also return nonzero status; inspect the error message when a command fails. The read-only status command exits successfully when it can read the evaluation, even when its saved decision is failing or inconclusive.

Capture and evaluation share one local coordinator lease. An active capture/resume or evaluation process blocks a competing coordinator before it dispatches work. Read-only summaries use one SQLite read snapshot and can be inspected while work is running.

Versioned behavior criteria

schema_version: 1
id: product-behavior
revision: draft-1
qualification: draft
scope: turn
streams: [primary]
sample: 0
criteria:
  - id: respects_user_constraints
    revision: draft-1
    rubric: >-
      Mark met when the answer's main steps respect the user's stated resources
      and limitations. Mark violated when an essential step contradicts them.
      Repeating a constraint in the introduction does not compensate for a
      procedure that depends on an unavailable resource.
    evidence_requirement: conversation
    allow_not_applicable: false

The first supported scope is one assistant turn with its saved conversation history. Every planned assistant turn in the selected streams gets a result slot, including an answer missing from an interrupted capture. The original dataset's answer is never substituted for a missing generated answer. streams defaults to [primary]; explicitly select [primary, compare] to evaluate both under the same budget. Decisions cover all selected streams; baseline/candidate comparison policies are separate future work.

Evidence requirements are explicit:

Requirement Eligibility
conversation A committed answer with its saved conversation
context A committed answer with provided context, including an explicitly empty object
complete_context Context declared complete by the capture contract

This table describes generic rubric eligibility. Built-in grounding and faithfulness also require a readable passage collection and nearest user question, as specified in their evidence contract.

Legacy observations have unknown evidence completeness, so they cannot satisfy complete_context. Provided context does not establish completeness or relevance. This evaluator sends the complete saved context without silently truncating it; an endpoint context-limit error remains unavailable work rather than a shortened evaluation. Future evidence selection must make its limits explicit.

The judge returns a strict JSON object:

{
  "outcome": "violated",
  "rationale": "The proposed essential step conflicts with the stated constraint.",
  "evidence": [{"source_id": "answer", "quote": "an exact quote from the saved answer"}]
}

Allowed outcomes are met, violated, and explicitly permitted not_applicable. Missing fields, unknown fields, malformed JSON, duplicate JSON keys, invented source IDs/quotes, and truncated or refused replies do not become successful verdicts. Met and violated verdicts require evidence. Source IDs identify answer, context, or a turn:<ordinal> from the supplied conversation. Quotes are checked against those exact saved sources; this verifies attribution, not the judge's reasoning or the claim's truth.

Conversation and retrieval content are marked as untrusted data in the judge prompt. That prompt boundary is not proof of resistance to injection. Product/judge qualification against reviewed adversarial and ordinary cases remains required.

An executable Atlas rubric draft covers resource constraints, practical task usefulness, and visible source support. It complements the Atlas acceptance design; it is not a reviewed gold set or a qualified judge. A draft rubric cannot produce a passing decision, including in criterion/tag/persona breakdowns. A recorded violation still produces failure. qualification: reviewed is an explicit owner declaration; the toolkit does not perform or independently verify the human qualification process.

Judge adapters

A factory is an importable zero-argument function that constructs an adapter without making evaluation requests. For an OpenAI-compatible local judge, for example:

import os
from openai import OpenAI
from alignmenter.providers.durable_judge import ChatCompletionJudge


def make_judge():
    client = OpenAI(
        base_url=os.environ["ALIGNMENTER_JUDGE_BASE_URL"],
        api_key=os.environ.get("ALIGNMENTER_LOCAL_API_KEY", "not-needed"),
    )
    return ChatCompletionJudge(
        client=client,
        model=os.environ["ALIGNMENTER_JUDGE_MODEL"],
        revision=os.environ["ALIGNMENTER_JUDGE_REVISION"],
        max_completion_tokens=2048,
        timeout=60,
        json_mode=True,
    )

The adapter disables SDK retries and does not fall back from JSON mode to another request. Set json_mode=False explicitly if the endpoint requires it; this changes the judge identity. The SDK provides max_retries=0 and per-client/per-request timeout configuration. Official Python SDK documentation.

Unsupported endpoint/model parameters yield a recorded failure without an implicit second request. The deployment revision must cover pinned model behavior and any custom client/transport configuration. The adapter additionally hashes the endpoint, model, SDK version, organization/project, timeout, token limit, and JSON mode. It cannot verify that a deployment matches its declared revision. Custom transports must not introduce their own hidden inference retries.

ChatCompletionJudge records returned usage but has no inferred pricing or monetary upper bound. Use its call-count budget or supply a bounded-cost custom adapter. There is no network dispatch during the repository's adapter tests: they use a mocked HTTP transport to verify success and one request on rate-limit failure.

Custom backends implement contract: JudgeContract and evaluate(request: JudgeRequest) -> JudgeReply, or use CallableJudge(function, contract). The contract declares a nonsecret configuration digest, model identity, at most one outbound dispatch per invocation, and an optional cost upper bound. Disable SDK retries, fallbacks, and implicit repair calls inside the adapter. Its well-typed reply preserves raw text, finish reason, optional usage, and optional actual cost. Invalid transport payloads are unavailable; a well-typed reply's raw verdict is saved before validation.

The configuration digest must cover the adapter code, deployed model, inference settings, transport policy, and any pricing assumptions. It is an adapter-owned declaration, not introspection of an arbitrary provider object. Credentials are not part of the snapshot. Provider exceptions store their class, not arbitrary messages or headers.

Budget and recovery semantics

The first judged evaluation requires an explicit --max-judge-calls. Deterministic grounding alone needs no judge or budget and leaves limits unconfigured. Later evaluations on the same capture use that frozen budget across criteria, selected streams, revisions, intentional samples, and restarts. Report grouping never reserves a call. Existing legacy scorer-local budgets are separate and retain their previous behavior.

Every new request is reserved in a transaction before dispatch. Reservations are conservative: a crash before the request reaches the provider still consumes one call. Completed identical requests can reuse a saved raw reply without another reservation. The cache key includes the exact request and judge contract; different rubric requests, judge configurations, or intentional sample values cannot silently share a reply.

Saved boundary Continuation
Validated verdict committed Reuse it directly
Raw reply committed, verdict not committed Validate the saved reply; no new judge call
Request reserved, no reply committed Record unknown_outcome; retain its budget charge
Invalid/truncated verdict Preserve the raw reply and invalid result; no automatic repair
Budget exhausted Record budget_blocked for the missing decision
Late reply to an abandoned request Reject it; it cannot overwrite a terminal call

The coordinator never repeats an unknown judge outcome automatically. To request an intentional new judgment, change sample and pass --new-evaluation. Changed rubrics, judge configuration, engine/package identity, or capture inputs also require explicit --new-evaluation. All such evaluations share the original remaining budget. Budget top-ups are not implemented in this slice. A changed rubric qualification can reuse unchanged judge requests while preserving the separate evaluation manifests.

Optional --max-judge-cost-micros uses integer millionths of USD and requires the adapter to declare a valid maximum cost per invocation. An estimate is not an upper bound. Unknown actual cost retains the full reservation; known actual cost replaces it for subsequent accounting. If the provider exceeds its declared bound, the actual cost is retained, the result is invalid, and further uncached dispatch is blocked. The toolkit cannot undo external spend caused by a false adapter declaration.

Reports distinguish known actual cost, calls with unknown actual cost, and accounted cost including reservations. A missing cost is never reported as a measured zero. Unknown limits/cost bounds cannot bypass a configured monetary budget.

Saved results and decisions

Rubric records live in the existing authoritative run.sqlite3. Explicit evaluation initialization adds a transactionally created extension with its own schema version, currently 1, to capture database version 2. Capture schemas, observations, and capture state are preserved. Read-only inspection does not create extension tables. Unsupported extension versions are rejected; version 1 capture databases remain read-only.

The extension stores evaluation manifests, frozen inputs, judge reservations/raw replies, and immutable per-criterion results, with content digests. A captured run can have several evaluation snapshots; inspecting an older evaluation uses its saved coverage even if capture was later resumed. A new evaluation against the additional capture requires an explicit new snapshot, and can reuse unchanged judge requests within the same budget.

Result states include met, violated, not_applicable, missing_capture, missing_evidence, invalid, budget_blocked, unknown_outcome, and built-in needs_review (unavailable interpretation). A not-yet-committed result appears as pending in summaries. Unavailable reasons are retained for inspection.

Coverage is judged applicable items divided by planned items excluding explicit not_applicable results. Met rate uses judged items only and must be read alongside coverage. With no applicable or judged items, the corresponding rate is unavailable. Any violation makes the decision fail. Passing requires at least one judged item, no unavailable applicable item, and a reviewed rubric. All other cases are inconclusive. This rule is applied consistently to the selected streams and all breakdowns.

The Python equivalents are evaluate_saved, evaluation_summary, and the typed contracts in alignmenter.schemas.evaluation. EvaluationStore exposes saved calls, results, manifests, and budget summaries for inspection. Its low-level mutation methods require coordinator ownership; the public execution service acquires the lease.

Grounding and faithfulness now have typed assessments and pure saved metrics on this path; see their migration notes, including inspection of older rubric-v1 evaluations. Remaining work includes adoption by legacy run, authenticity/safety migration, broader evaluator scopes, qualified judges, budget revisions. Saved comparison statistics, offline HTML review, human annotation, and CI gates are available in the 0.3 release workflow.