GitHubBook a demoStart routing
Browse docs

Evals & calibration configuration

Field-by-field reference for the evals: block — outcome ingestion, evaluator instances, judge validation, quality calibration, and drift detectors — plus the dashboard.capture dependency that eval runs read.

Last updated

On this page

The evals: block (internal/config/evals.go, §2.11) configures the measured-quality substrate: outcome ingestion, the declared evaluator instances, the judge-validation posture, quality calibration, and the drift detectors. It is additive over every prior schema — an absent block compiles byte-identically to a pre-feature config.

This block fills no defaults. ApplyDefaults leaves an Evals value byte-identical to what was decoded. The leaves are the parameters of a statistical claim — confidence is the claim, not an operational knob — so a leaf a claim depends on is required when its block is on, and a leaf whose absence has a true reading (min_n ⇒ the named floor; refit_interval ⇒ no scheduled refit) is left absent and read that way. Nothing is ever filled in behind the operator.

evals.outcomes

The two ingestion doors. Both are off by default (an ingestion endpoint that exists is one that can be written to), and both require dashboard.enabled — the management API that serves them is mounted only when the dashboard is on; a flag set against a disabled dashboard is refused by name.

Field Type Default Meaning
api_enabled bool false Serves POST /tt/api/v1/outcomes (typed batch ingestion). Requires dashboard.enabled.
otel_ingest bool false Serves POST /tt/api/v1/outcomes/otel (OTLP/HTTP JSON gen_ai.evaluation.result). Outcomes arriving this way are basis: reported by definition — enabling it never widens what may be claimed, only what may be recorded. Requires dashboard.enabled.

evals.evaluators[]

The declared scorer instances, in declaration order. Each names a registered evaluator id (§2.3) and carries its own sampling and budget discipline. An instance is one scorer: declaring both a judge: and a sidecar: block under one id is refused.

Field Type Required Meaning
id string yes Names a registered evaluator descriptor. Unique within the list.
sample_rate float yes Fraction of eligible requests this evaluator scores, in [0,1]. Not defaulted — 0 (never) and 1 (every eligible request) are both plausible readings of an omission, so it is refused rather than guessed. 0 is legal and means “declared but not sampling”.
max_usd_per_day float when judge: present Per-evaluator daily spend cap (USD). Mandatory whenever judge: is present; optional otherwise. Must be finite and >= 0 (0 stops the evaluator at its first priced call; .inf is refused — an infinite cap is not a cap). Enforced post-hoc.
judge object Configures the LLM-judge kind. Its presence is what makes the instance metered. See judge.
sidecar object Configures the operator-run scorer-service client. See sidecar.

evals.evaluators[].judge

An LLM judge phones out to its provider on every sampled request. Its outcomes are basis: reported until the B.7 validation protocol promotes them.

Field Type Required Meaning
external string yes The air-gap gate (SP-16). Must be the literal allow or the whole evaluator entry is refused at config validation. Any other value — including a typo or true — leaves the gate closed. The comparison is != "allow", never == "".
model string yes The judge model id sent to the provider. An empty value is refused; so is the literal oracle (the shadow block’s grading-path selector — sending it as a model id is a recorded wiring bug). Use a different model family than the tiers being judged (B.7 refuses a same-family judge).
api_key_env string yes Names the environment variable holding the judge provider’s API key (uppercase, e.g. ANTHROPIC_API_KEY). It must never contain the key — the running config is served verbatim to any principal holding config:read.

evals.evaluators[].sidecar

An operator-run scorer service (e.g. an NLI/HHEM endpoint). It POSTs captured request and response bodies to the service, so it is egress and is gated like the judge.

Field Type Required Meaning
external string yes The air-gap gate. Must be the literal allow or the entry is refused.
endpoint_env string yes Names the environment variable holding the sidecar base URL (e.g. HHEM_ENDPOINT). A literal URL is refused at config validation — an endpoint routinely carries its own authorization in host or path.

evals.validation

Field Type Default Meaning
require_iaa bool false A raise-only judge-validation knob (B.7). false admits a single-annotator gold set with its disclosure (iaa: null, unmeasured). true upgrades that disclosure to a refusal: a single-annotator gold set then cannot license judge promotion. Neither value weakens the κ ≥ 0.6 floor that already applies wherever IAA is measured.

evals.calibration.quality

Configures the quality-calibration fitter (§2.4) that turns raw evaluator scores into probabilities whose error is measured on a held-out slice. See How calibration works.

Field Type Required Meaning
enabled bool Gates fitting. false ⇒ no artifact is written and no calibrated claim can be emitted at all.
confidence float when enabled The nominal coverage level 1−α of the intervals the fitter produces (0.90 ⇒ α = 0.10). No default — it is the claim, and this package will not choose an operator’s α. Must be strictly between 0 and 1 whenever present.
min_n int The per-segment refuse-below-N floor. Raise-only: absent ⇒ the named floor 50 (calib.DefaultMinSegmentN) applies; a configured value below 50 is refused. It floors the calibration split, so a segment needs ≈ 4× this many fit-eligible rows (~200) before any claim is licensable.
refit_interval duration The scheduled refit cadence (e.g. 24h). Omit (or 0s) ⇒ no periodic refit; the drift, policy-epoch, and staleness triggers still fire. A negative value is refused.

evals.drift

Field Type Default Meaning
detectors string[] [] Registered drift-detector ids (e.g. robust-seasonal-v1, psi-window-v1), each non-empty and unique. Empty ⇒ drift is UNMEASURED, not “no drift” — the drift-clean precondition of a calibrated claim (DQ4) is then satisfied vacuously rather than checked. Configuring an empty list while calibration.quality.enabled is on emits a validation warning.

The dashboard.capture dependency

evals run and sidecar evaluators re-score stored request/response bodies. Capture defaults to hash-only, so a default deployment’s evals run finds no bodies and returns a counted refusal naming config.DashboardCapture.response — a documented default state, not an error. Turn on capture to make eval runs executable:

dashboard:
  enabled: true
  capture:
    request: true      # default false (hash-only)
    response: true     # default false (hash-only)
    ttl: 720h          # default 720h; 0 ⇒ until retention sweeps
    path: ~/.tokentriage/content   # default ~/.tokentriage/content
Field Type Default Meaning
request bool false Capture request bodies to the content store.
response bool false Capture response bodies — the ones an eval run re-scores.
ttl duration 720h Retention of captured bodies. 0 ⇒ kept until retention sweeps.
path string ~/.tokentriage/content Content-store directory.

A worked example

evals:
  outcomes:
    api_enabled: true        # POST /tt/api/v1/outcomes
    otel_ingest: false       # POST /tt/api/v1/outcomes/otel (basis: reported)
  evaluators:
    - id: code-output-nonempty     # CODE-kind: local, free — only id + sample_rate
      sample_rate: 1.0
    - id: judge-groundedness       # JUDGE (external LLM): metered ⇒ budget mandatory
      judge:
        external: allow
        model: gemini-2.5-flash    # a DIFFERENT family than the gpt/claude tiers judged
        api_key_env: JUDGE_API_KEY
      sample_rate: 0.05
      max_usd_per_day: 5.0
    - id: nli-groundedness         # SIDECAR (operator-run scorer service)
      sidecar:
        external: allow
        endpoint_env: HHEM_ENDPOINT
      sample_rate: 0.10
  validation:
    require_iaa: false             # raise-only judge-validation gate
  calibration:
    quality:
      enabled: true
      confidence: 0.90             # REQUIRED when enabled; no default — it IS the claim
      min_n: 50                    # raise-only; 50 is the floor and the default
      refit_interval: 24h          # omit/0s ⇒ no periodic refit
  drift:
    detectors: [robust-seasonal-v1, psi-window-v1]   # empty ⇒ drift UNMEASURED

Six end-to-end configurations ship as examples/evals/journey-{1..6}-*.yaml, walked through in examples/evals/README.md.