View Markdown

Route only to models with quality evidence (experimental, opt-in)

On this page

quality-guarded serves a cheaper model only where you have evidence it is good enough for the task. It keeps three things apart: quality (labels from a validator or a reviewed gold set, never latency, price, HTTP success or silence), reliability (the existing health, breaker and ladder machinery), and cost (declared prices, used only to rank models that are already qualified).

Status: source-complete and offline-verified only. Unit, router and end-to-end tests on the fast suite pass and the safety checks are mutation-proven; the full estate, live evaluation, statistical qualification of any real model, enabled-mode endurance and release promotion are NOT RUN. Treat it as experimental. Design and acceptance mapping: design guide.

What it does on each request#

  1. The group's strategy reads the active quality policy from memory (refreshed in the background; selection never touches the database or calls a learner, judge or classifier).
  2. It picks the request's task profile: the group's default, or - only when that profile says it trusts caller hints - the profile a valid X-Task-Type names. A hint can never select a profile that does not accept hints.
  3. Qualified now = the profile's qualified models whose evidence has not expired and whose registry row still matches the row the evidence was bound to. An expired policy, or an organization outside the profile's scope, qualifies nothing.
  4. Among the candidates that survived every existing filter (permissions, capability, health, budget) and are qualified now, it picks the lowest declared price; an undeclared price ranks last, never as free. Optional exploration (at most 20%, off by default) samples among qualified models only, from trusted label counts; unknown counts switch it off. The counts are those of the request's own qualification: each label counts under the serving key recorded when its decision was served. That key binds organization, profile and its qualification epochs, rubric, model version, registry row and pipeline. Labels from an earlier qualification never steer a new one, and counts published under another policy version than the request's are unavailable, not zero. A profile may instead declare "selection":"bandit": the qualified candidates (and only those) go to the reward-learning bandit, which prefers among them by observed outcomes - success, latency, cost, ratings - and adapts when a provider degrades or recovers. The quality boundary still binds every fallback hop. Without pointsman.bandit.enabled=true the profile ranks by price and counts pointsman.quality.decisions{outcome="bandit_unavailable"}. Rewards never qualify a model: qualification stays the manual, evidence-bound step above. "selection":"cost" instead hands them to the cost sampler: Thompson sampling on each qualified model's OBSERVED cost of a completed call (the registry's cache-aware price of the usage the provider reported), started from the declared prices. On day one it ranks as the prices do; when a model's bill drifts from its price (it writes or reasons more, a prompt-cache discount, repeated failures that are paid again on the fallback) it moves within a few hundred requests and comes back when that stops. State is per organisation and profile, in-process (one instance); a restart starts again from the prices. Latency is not in its score - use the qualification's latency.max_ms gate for that. It consumes no quality labels: "completed" means the provider answered, not that the answer was right, so a qualified model that later degrades is not detected - re-qualify on a schedule. Experimental and opt-in. A cost profile may add "feedback_floor": 0.8: labels posted with a response's X-Quality-Receipt to POST /v1/quality/assessments (first revisions only; voluntary or trusted) are counted per model with forgetting, and a model with at least 8 recent labels whose Wilson 95% upper bound on labelled accuracy is below the floor is no longer chosen (all of them below: the declared baseline serves). Labels only ever remove a qualified model, only for the labelling organisation's own profile; a removed model is probed again as its labels age. The removal is part of the request's quality boundary (17 October 2026), so it binds every path, not only the cost choice: exploration, session affinity, family preference and every fallback hop. The declared baseline chain is never removed. One request is judged against one policy version: the boundary the router computed before its shortcuts is the one the choice and the ladder use, and the decision record carries feedback_floor and feedback_excluded.
  5. No qualified candidate survived: the declared baseline, then its declared fallbacks. None survived either: 503 quality_policy_unavailable. Never a random or unqualified model.
  6. Session affinity, family preference and every fallback hop are held to the same boundary; the group never escalates to a neighbouring group; the response cache is bypassed.
  7. The response says what served it, and carries a signed receipt for later evidence.
Response header Meaning
X-Quality-Route: qualified; profile=<id>; policy=<n> a qualified model served
X-Quality-Route: baseline; profile=<id>; policy=<n>; reason=<r> the declared baseline served; r is none_qualified, no_qualified_eligible, evidence_stale, policy_expired, profile_out_of_scope or fallback_to_baseline
X-Quality-Receipt: q1.<payload>.<signature> cite this when submitting evidence; binds organization, decision, served model, profile, policy and serving conditions

A request with no policy loaded is refused with 503 and code: quality_policy_unavailable (reason in the message: policy_absent, no_profile or no_eligible_baseline).

Turn it on#

bash
POINTSMAN_QUALITY_ENABLED=true

and route a group to it in routing.yml:

yaml
pointsman:
  routing:
    groups:
      support-extract:
        strategy: quality-guarded
        candidates:
          - { provider: openai, model: gpt-4o-mini }
          - { provider: openai, model: gpt-4o }

Until a policy is promoted the group refuses every request. Every other setting, with its unit, bound and default, is in the configuration reference under Optional quality-guarded routing.

1. Register a validator (organization administrator)#

Only a registered key may submit trusted evidence, for its own organization and listed rubrics.

bash
curl -u admin:pw -H 'Content-Type: application/json' \
  -X POST localhost:8080/admin/quality/orgs/$ORG/validators \
  -d '{"key_id":"'$VALIDATOR_KEY_ID'","name":"fields-checker","rubrics":["fields"]}'

2. Freeze an evaluation and adjudicate one sealed batch (operator)#

The spec fixes everything the verdict depends on - profile, rubric, baseline, candidates, required groups, q_min, delta, severe_max, confidence, method, design, the EXPECTED COHORT (the manifest), evidence lifetime, scope and pipeline - BEFORE you look at results. There are no defaults; the numbers below are an illustration, not a recommendation.

json
{"schema":"pointsman.quality-eval-spec/v3","profile_id":"support-extract",
 "rubric":{"id":"fields","version":"3"},
 "baseline":{"model":"gpt-4o","model_version":"2024-08-06"},
 "candidates":[{"model":"gpt-4o-mini","model_version":"2024-07-18"}],
 "groups":["en","de"],"q_min":0.95,"delta":0.02,"severe_max":0.01,"confidence":0.95,
 "method":"clopper-pearson-bonferroni-v1","design":"independent",
 "evidence_ttl_days":90,"scope":{"orgs":[]},
 "pipeline":{"task_source":"group_default","task_types":[],"baseline_fallbacks":[],"cascade":"none"},
 "manifest":{"tasks":[{"task_id":"inv-0001","group":"en","content_sha256":"<sha256 of the task as evaluated>"},
                     {"task_id":"inv-0002","group":"de","content_sha256":"..."}, ...]},
 "illustrative":false}

POST /admin/quality/epochs registers it (the same spec twice returns the same epoch). POST /admin/quality/epochs/{id}/adjudicate takes ONE batch ({"schema":"pointsman.quality-eval-batch/v2","rows":[...]}) of labelled rows - one row per task per model, outcome success, failure or missing - and records the verdict forever.

Frozen cohort membership#

The cohort is frozen, not described. The batch must be EXACTLY the manifest: every expected (task, model) pair once - a failure, a timeout and an absent label are all rows (missing for the last two) - and nothing else. A row for a task the manifest does not list, a task in another group than the manifest says, or an expected pair left out refuses the batch: an export that drops its failures cannot qualify a model by omission.

Complete model/task coverage#

Every compared model answers every task. Qualification compares the baseline and each candidate on the SAME complete task population in every group. A task whose models subset leaves out the baseline or a candidate is refused as an unsupported design (spec_unsupported). That covers a candidate run on the easy half, disjoint populations and partial overlap. Such a cohort is never intersected away. Independently randomised populations are not certified by this checker. An epoch registered before this rule with such a cohort keeps its verdict readable and cannot promote (legacy_unpaired_cohort).

Corpus identity#

The corpus identity is the membership, not the order. corpus_sha256 is derived by the server as the order-independent identity of the manifest's entries: task id, group, model set and content_sha256 when declared. The same tasks in another order are the same corpus, and so the same repeated look. The model set is canonical as well: leaving models out, listing every compared model, or listing them in another order or with a repeat all mean "every compared model" and give one identity. The ordered list is reported separately as execution_identity. Every change to a task, group, model or content hash is a different corpus. Declare content_sha256 per task when the task content lives outside the gateway: without it, the identity binds task ids only, and the verdict says so, because an id does not prove two evaluations saw the same prompt.

v3 is the registrable schema. Stored v2 epochs keep their ordered corpus_sha256 unchanged, adjudicate and promote under the same checks; new v2 registrations are refused (spec_schema_superseded). Repeated looks are counted by the canonical identity across every stored epoch, so restating a seen corpus in another order or under another schema version is a repeated look. v1 specs (a self-declared corpus hash) are no longer accepted; v1 epochs stay readable and never promote. The epoch view reports corpus_identity, execution_identity and content_bound.

Evaluated model identity#

The evaluated identity is captured, not assumed. At registration the server records the registry digest of every model the spec evaluates (each must be in the registry). The batch is refused if any of those rows changed while the evaluation ran, and promotion compares the CURRENT row with the EVALUATED one - so an alias repointed after the evaluation cannot inherit its verdict, even on the first promotion. A price change does not count as a change: it moves cost ordering, not answer quality.

Pipeline identity#

pipeline names everything the gateway controls about how a task reached the model: the task source and classifier version, the task_types mapping that routes labels to the profile, the baseline's declared fallbacks, and the cascade verifier between answer and client ("none" without one). Promotion requires the profile to serve exactly that pipeline (and that baseline), and serving re-checks it on every request - together with the EVALUATED baseline's registry identity, so a baseline alias repointed after promotion makes the candidate's evidence stale rather than relative to a baseline nobody compared it with (price-only changes excepted). Declared baseline fallbacks are part of the pipeline by name only: they are operational, never evaluated identities, so switching a group's cascade on after promotion makes its evidence stale. Your prompts and generation parameters are not gateway configuration and are NOT bound: evidence assumes your traffic looks like the evaluated corpus.

How the verdict is reached: every bound the decision uses (per group, the candidate's success and severe-error rates and the baseline's success rate) is an exact Clopper-Pearson interval at a Bonferroni-allocated level, so they hold simultaneously at the spec's confidence. A candidate is QUALIFIED only if, in every required group, its lower success bound is at least q_min, it is within delta of the baseline's upper bound, and its severe-error upper bound is at most severe_max. It is REJECTED when the evidence shows it fails; INSUFFICIENT when the evidence cannot tell (an empty group is never a pass); INCOMPLETE when any selected row has no label. Duplicated tasks, declared clusters and weighted or sequential designs are refused as UNSUPPORTED rather than counted as independent. Price is never an input: a cheap model qualifies exactly when its labels support it.

Optional latency gate#

Add "latency":{"quantile":0.95,"ratio_max":1.5} to the spec and every labelled row carries latency_ms (end-to-end; a batch missing one is refused). The candidate then also needs its p95 (here) proved within 1.5x the baseline's, on the same frozen tasks: the upper bound of its p95 must be at most ratio_max times the LOWER bound of the baseline's. It is REJECTED when its lower bound already exceeds ratio_max times the baseline's upper bound, and INSUFFICIENT in between. "max_ms":5000 instead (or as well) is an absolute SLO for the surface: the candidate's upper p95 bound must be at most 5 s, and a lower bound above 5 s rejects it - no baseline interval is needed for that part. The bounds are exact, distribution-free order-statistic intervals counted in the same Bonferroni family (one per candidate plus one for the baseline), so the spec's confidence still covers every bound at once. Latency only ever removes a qualification; a fast model with failing labels stays REJECTED. It is a gate, not a learning signal: the bandit (with selection: bandit) then adapts only among models that already met it. Audit windows cannot declare it (routing chose who answered what, so the two latency populations are not comparable). Without latency the spec, its hash and the verdict are exactly what they were.

One adjudication per epoch, ATOMICALLY: two submissions that race are finalized once (an optimistic lock on the epoch); the other - like any later submission - gets 409 epoch_already_adjudicated, which says same_batch=true|false, and leaves no audit record. A promotion running meanwhile sees the epoch as not adjudicated until the winner commits. Changing a threshold is a new spec; a new spec on a corpus that was already adjudicated is marked respecified_after_look and can never support promotion - the repeated-look pattern a fixed-sample interval does not cover. Looks are counted again at promotion: if another epoch on the same corpus was adjudicated before this one, the promotion is refused ("repeated looks, counted at promotion"), even when both were registered before either was adjudicated. Only the first adjudication of a corpus can promote. Specs marked illustrative can never promote either.

3. Preview, then promote a policy (operator)#

json
{"schema":"pointsman.quality-policy/v1","expires_at":"2026-12-31T00:00:00Z",
 "profiles":[{"id":"support-extract","group":"support-extract","default_for_group":true,
   "rubric":{"id":"fields","version":"3"},"baseline":"gpt-4o","baseline_fallbacks":[],
   "orgs":[],"hints":{"trust":"none","task_types":[]},
   "pipeline":{"task_source":"group_default"},"exploration":{"epsilon":0.0},
   "qualified":[{"model":"gpt-4o-mini","model_version":"2024-07-18","epoch_id":"<epoch>"}]}]}

POST /admin/quality/policy/preview shows every error, warning and the change against the active version. The server - not the draft - binds each qualified entry: it checks the epoch (adjudicated, not illustrative, not re-specified, same profile, rubric and pipeline, the candidate QUALIFIED, evidence unexpired, scope compatible) and writes the registry digest, spec hash and evidence expiry itself. A draft that asserts its own binding is refused.

POST /admin/quality/policy/promote with {"document": ..., "expected_version": <active or null>, "reason": "..."} creates the next immutable version and reloads every instance through the control-plane channel. POST /admin/quality/policy/rollback with target_version re-validates that old document today: evidence that has expired since, or a registry row that has changed, refuses the rollback unless you pass "drop_expired": true, which removes those entries. Rollback never revives expired evidence. GET /admin/quality/status shows what every instance is actually enforcing.

4. Submit evidence#

POST /v1/quality/assessments with an API key:

json
{"receipt":"q1....","rubric_id":"fields","rubric_version":"3","outcome":"failure","severe":false,
 "source":"trusted_validator","external_id":"job-8812","revision":0,"lane":"audit",
 "inclusion_probability":0.05,"evidence_ref":"s3://audits/8812"}
  • source is checked by the server: trusted_validator/reviewed_gold only from a registered validator key; any other key of the organization may send voluntary; another organization's receipt does not verify.
  • One identity (source, validator, external_id) and revision is stored once; the same payload again is a duplicate (200), a different payload is 409; a correction is a higher revision.
  • No weights, counts or scores are accepted, and no payload: evidence_ref is an opaque reference.
  • Trusted, judge-proxy and voluntary evidence are kept in separate namespaces and never mixed; voluntary ratings are self-selected and never used for qualification. Stored evidence supports qualification only through a sealed batch; this call does not change routing.

5. Evidence without rating every response: the audit lane#

Set POINTSMAN_QUALITY_AUDIT_ENABLED=true and a fixed POINTSMAN_QUALITY_AUDIT_SAMPLE_RATE (say 0.05). At serve time - before any outcome exists - that share of quality-guarded responses is SELECTED and the response says so: X-Quality-Audit: selected; lane=audit; p=0.05. Your application's validator labels those decisions with lane: "audit" and inclusion_probability equal to p. Every selection is recorded (GET /admin/quality/audit): selected, completed, ungraded, failed, expired, and selections DROPPED because the bounded queue was full (tallied per profile-hour). p is the probability of selection, never of a label having been obtained.

To turn a window of audits into evidence, register the window BEFORE it opens: a v2 spec with "audit_window":{"from":"...","to":"..."} instead of a manifest, groups ["all"], and scope.orgs naming the organizations it samples (a window starting at or before registration is refused: it may already have been looked at). After it closes, POST /admin/quality/epochs/{id}/seal-audit-window. Every in-scope selection becomes a row; a selection with no trusted label - including one only a judge looked at - is missing, so the cohort is INCOMPLETE rather than quietly smaller. Selections from other organizations are outside the preregistered frame and are excluded (and counted). Each selection records what ACTUALLY served it - the registry digest of the serving row and the profile's pipeline - and sealing refuses a window in which an alias served a different identity than the one evaluated, or a different pipeline, rather than relabelling it. A window with any dropped selection is refused.

Audit sealing deadline#

Seal within pointsman.quality.audit.retention-days of the window's START. The daily audit purge deletes finished jobs older than retention-days (default 30) and does not know which windows are still unsealed. A window sealed later than that adjudicates only the jobs that survived: a quietly smaller cohort, not an INCOMPLETE one, and the adjudication is final. Because an audit window never promotes, this cannot qualify a model, but the recorded counts and bounds would be wrong. Keep windows shorter than retention-days and seal promptly. A seal-time refusal is the intended fix; it is not in this release.

An audit window is OBSERVATIONAL: routing chose which model answered which request, so candidate and baseline cells hold different task populations. Its verdicts keep every count and bound and can REJECT or be INCOMPLETE, but QUALIFIED becomes INSUFFICIENT and the epoch never promotes. Qualification comes from a frozen manifest where every model answers the same tasks.

The optional judge (proxy only)#

POINTSMAN_QUALITY_JUDGE_ENABLED=true with an explicit provider and model, payload consent, and an enabled operator account. The judge reads the user turns and the answer (system and developer messages and tool definitions are never sent), fenced as untrusted data, with no tools, and must answer exactly {"verdict": "pass"|"fail"|"abstain", "severe": bool}; anything else is UNKNOWN. It never judges its own answer, it is called through the provider adapter directly (no router, cache, reward or tenant charge), and its verdicts land in the PROXY namespace, which never supports qualification in this release. Calibrate it against reviewed gold labels (false acceptance bounded) before trusting it for anything; even then it stays a proxy.

The operator-funded auxiliary account#

Judge, audit, classifier, alternative-answer and cascade attempts reserve against ONE operator account held in Postgres (POINTSMAN_QUALITY_AUX_*): every limit is fleet-wide and survives restarts.

Auxiliary attempt reservations#

Each attempt reserves its own estimated bound, under a deliberately narrow contract. An auxiliary attempt is admitted only when all three hold, and is refused BEFORE anything is sent otherwise:

  • the provider's adapter type is declared in POINTSMAN_QUALITY_AUX_BOUNDED_ADAPTERS (default openai,openai_compatible: OpenAI-wire adapters billed per prompt and completion token);
  • the request is plain text chat: roles system/user/assistant with text content, no tools, tool choice, tool calls or tool results, no images, audio, output modalities, predicted output, web search or response format, service_tier absent, auto or default, not streaming;
  • the row is priced per 1k input and output tokens, with no cache-write price above its input price.

The bound is the UTF-8 bytes of every billed field of that form (role, content, name) plus a framing allowance, plus the output cap the gateway writes onto the outbound request (the tighter caller cap, else the row's maximum output) times n, at the rates captured for that attempt. It is an upper bound ONLY under the stated assumption of a byte-level tokenizer (never more than one token per byte) whose chat framing fits the allowance; it is not a universal hard limit, and a provider that breaks the assumption shows up as an overrun that fences the account. The attempt's pricing identity (model and rates) is recorded with its reservation and its observed usage is priced with those same rates, so a registry edit mid-attempt changes neither. MAX_ATTEMPT_MICROS caps one attempt's bound; MAX_SPEND_MICROS caps spent + reserved + unknown across the fleet, so two replicas racing cannot jointly admit more liability than the account holds.

Dispatch controls#

Every send is one governed dispatch. Judge, alternative-answer and cascade calls go through the same single-attempt seam: provider capacity, one observation of the credential pool (a configured pool with no usable key refuses - it never falls through to the adapter's default key - and a key selected for the attempt that disappears before the adapter uses it refuses rather than being replaced by another), breaker admission taken only after the key is chosen (an OPEN breaker, or a HALF_OPEN breaker whose probe permit is taken, refuses before sending; a refusal before sending gives the permit back), exactly one breaker outcome per real send, the outbound tally kept and a rate-limited key cooled. One send, never a hidden retry or failover.

Transactional admission#

The account mutation, the attempt row and its pricing identity are written together; a failure inside it rolls the whole admission back (refused, nothing sent). A failure while COMMITTING is reported as uncertain, never as a rollback: nothing is sent, and if the reservation did commit it expires into unknown exposure rather than vanishing.

Estimation boundary#

A conditional conservative ESTIMATE for the declared adapters under their tokenizer and framing assumptions - not an absolute invoice-spend guarantee and not a universal provider bound. openai_compatible names a wire protocol, not a tokenizer or a pricing guarantee: declaring an adapter bounded is the operator's claim about that provider, and a live evaluation permit must name the concrete providers, models and assumptions it tests.

A call whose cost was not observed after it may have executed (a timeout, an interrupted response, a crash before settlement) keeps its WHOLE bound as UNKNOWN exposure - never refunded, never zero - and new attempts stop once that exposure passes MAX_UNKNOWN_MICROS. A call proven never sent (connection refused) releases its reservation. An observed cost above its bound is recorded as spent, never clipped, and FENCES the account - whether the cost is observed at settlement or later at reconciliation of an UNKNOWN attempt: nothing more is admitted until POST /admin/quality/auxiliary/unfence {"reason": "..."} (the overrun stays recorded). GET /admin/quality/auxiliary shows calls, spent, reserved, unknown exposure, the per-attempt ceiling and the fence; POST /admin/quality/auxiliary/attempts/{id}/reconcile settles one with a reviewed amount. No tenant budget, usage row or invoice is touched.

6. Task profiles from Jev, observation before enforcement#

Jev task attribution#

A profile with "pipeline": {"task_source": "jev", "classifier": "jev-1.13.0"} and hints.task_types: ["extract"] is chosen when Jev labels the request extract - with Jev's own opt-ins (enabled, the tenant listed in allowed-tenants) and bounds, one call per request, and never for a request that declares tools. The label chooses a PROFILE, never eligibility: a confident misclassification lands in that profile's own qualified set and nowhere else. Abstention, Jev off, a tenant not opted in, or a classifier version other than the one the profile names all fall back to the group default, and the reason travels in the decision record (profile_reason: jev:<task>, jev_unavailable, jev_low_confidence, classifier_version_mismatch, ...). The profile's evidence must have been gathered through the same pipeline: promotion refuses an epoch whose spec names a different task source or classifier. Jev confidence is a classification signal, not answer correctness.

Observe decisions#

POINTSMAN_QUALITY_OBSERVATION_DECISION_ONLY=true, plus a policy profile for a group that still uses another strategy: every decision there also records what quality-guarded WOULD have served (GET /admin/quality/observation, a bounded buffer, and pointsman.quality.observation agree/disagree counts). Nothing served, billed or learned changes, and no remote call is made (groups with Jev-sourced profiles are skipped).

Generate alternative answers#

A separate, paid permission (..._ALTERNATIVE_ANSWERS=true, payload consent, an enabled operator account, a rate of at most 0.1 and a per-minute cap): for sampled disagreements, the would-be qualified model is asked too, in the background, through the provider adapter. Only its cost and status are kept - the content is dropped, so it can never leak into evidence, learning, the cache or a held-out set.

Evaluation support#

A quality decision logs propensity 1.0 when it is deterministic (exploration 0, price selection) and NULL otherwise, with propensity_kind: unknown_marginal, the branch, epsilon and the qualified set in its decision record. Under selection: bandit the bandit logs the decision itself, with its sampled snapshot, as it does for any bandit decision. The offline estimator (policy_service/ope.py) refuses those rows and also refuses the legacy bandit exploration-floor rows' logged 1/n, which is conditional on the floor branch; marginal_propensity reconstructs the policy's marginal eps/n + (1 - eps) * P_thompson from the logged snapshot and labels it reconstructed_mc.

7. The bounded cascade (experimental, off)#

For a listed quality-guarded group, the cascade makes release depend on a FORMAT check of the answer itself. The routed answer is HELD and never written until a deterministic format verifier accepts it. On reject or abstain, ONE escalation to the profile's first baseline model runs, and its answer is released only if the same verifier accepts it. Anything else is refused. Nothing provisional ever reaches the client.

Verifier scope#

A format check, not a correctness check. The verifiers decide whether an answer has the shape the task needs. A well-formed WRONG answer - the wrong total in valid JSON, an allowed but wrong label - passes. That is why an acceptance is disclosed as check=format, writes no quality event and moves no success count: it is never quality evidence. Semantic verification (checking content against trusted, request-specific context under a versioned rubric) is NOT IMPLEMENTED; a semantic:, judge: or llm... verifier refuses startup by name. Because a cascade group's evaluated pipeline includes its verifier, the spec that qualifies its cheaper model must name the same verifier in pipeline.cascade (section 2).

bash
POINTSMAN_QUALITY_CASCADE_ENABLED=true
POINTSMAN_QUALITY_CASCADE_GROUPS=support-extract          # quality-guarded groups only
POINTSMAN_QUALITY_CASCADE_VERIFIER=json-fields:total,currency
POINTSMAN_QUALITY_CASCADE_MAX_STAGE_MICROS=20000          # $0.02 upper bound on the escalation stage
POINTSMAN_QUALITY_CASCADE_ESCALATION_TIMEOUT_MS=30000
# plus an enabled operator account (section 5): the escalation is paid from it

The verifiers are local and deterministic, with no model and no network:

  • json-object: the whole answer is one strict JSON object. A code fence, trailing text or a duplicate key is a rejection.
  • json-fields:<f,...>: the same, and every named top-level field is present and not null.
  • exact-choice:<A|B|...>: the trimmed answer is exactly one of the labels. Case matters.

A verifier abstains when there is no complete text to check: a tool call, no choices, more than 65,536 characters, or a provider that cut the answer off (finish_reason: length) or filtered it (content_filter) - a truncated JSON object that happens to parse is not accepted. A model judge is deliberately not a verifier here. It is uncalibrated, and a cascade must not judge recursively.

Escalation eligibility#

The escalation is a destination like any other. Before it is called, the baseline must pass the same per-hop test the router applies to every fallback hop, for this caller and this request: the effective allowed set, lifecycle, capabilities and context fit against the request's pinned admission context, then live health (breaker and a usable credential). Its registry row must be the one admission pinned. Failing any of these is a refusal before any call (escalation_not_eligible or escalation_unavailable); operator funding never authorizes a destination the caller could not use. The outbound escalation carries an explicit max_tokens - the one its cost bound was computed from (section 5).

Outcome (X-Quality-Cascade / refusal reason) What happened Billed
primary_accepted; stages=1; check=format the routed answer passed the format check tenant: the one call
escalated_accepted; stages=2; check=format the routed answer failed; the baseline's passed and was served (X-Quality-Route: baseline) tenant: the primary call; operator: the escalation
502 quality_cascade_unverified, final_rejected / final_abstained neither answer passed tenant: the primary; operator: the escalation
502, no_escalation the routed answer failed and was already the baseline (or nothing is qualified) tenant: the primary
502, escalation_unsupported the escalation is outside the supported cost contract (adapter, request form or pricing; section 5) tenant: the primary
502, escalation_not_eligible the baseline is outside what this caller may use for this request (allowed set, lifecycle, capability, context) tenant: the primary
502, escalation_unfunded / escalation_over_ceiling / escalation_unbounded / escalation_unavailable the escalation could not be paid for, bounded in advance (unpriced, no max_tokens and no row max output, image or audio) or reached (provider missing, unhealthy, row changed since admission) tenant: the primary
502, escalation_failed the escalation timed out or failed; a timeout stays on the account as UNKNOWN exposure tenant: the primary; operator: possibly the escalation
502, verifier_failed the verifier itself threw; nothing escalates tenant: the primary
400 quality_cascade_unsupported a stream or a request declaring tools: neither can wait for verification nothing; refused before any provider call

The tenant pays for the primary stage even when nothing is released. The model was asked and answered, the same as any completed call. The escalation is extra work the gateway chose to do, so it is never charged to a tenant. An escalated release teaches no learner: the primary arm's answer was rejected, so reporting it as served would be a hidden success assumption. The escalated answer passes the same output guardrails as the primary.

Turn it on only after you have measured it against single-model routing on your own workload: the cost of the escalations, how often each verifier rejects and abstains, and the refusal rate your clients will see. None of that has been measured here.

What it does not promise#

  • No evidence means baseline routing and "quality unknown", not a cheaper model.
  • Qualification covers the recorded task distribution, rubric, model versions and pipeline. It is not a per-answer guarantee and does not transfer to a new model version, rubric or classifier.
  • A judge's score is a proxy until a separate calibration says otherwise; prompt-injection mitigations reduce, not remove, the risk of a manipulated score.
  • A queued audit selection that was not yet persisted when the process died is lost; a graceful shutdown persists the queue instead. Overload drops are counted, never silent.
  • A DEGRADE limit rule never carries a request out of a quality-guarded group: the substitution is refused (503 quality_policy_unavailable, reason degrade_leaves_quality_contract) before anything is resolved, reserved or sent. Groups on other strategies degrade exactly as before. There is no waiver to serve a degraded quality request outside its contract.
  • The cascade's verifiers check FORMAT (JSON, fields, a label), not truth. A well-formed wrong answer passes. Semantic cascade verification is not implemented. The cascade is not a quality measurement and has no live validation: it is experimental and off by default.
  • Evidence binds the model's registry identity and the gateway-side pipeline, not your prompt templates or generation parameters: if your traffic changes, the evaluated task distribution no longer describes it.
Pointsman v1 candidate documentation Source: 0e09f5e7

Search guides, reference pages and architecture.

Documentation

Open full-size image