ModelRig Quickstart Routing & reliability Route bundles Probes Bake-offs & replay How it fits Grade protocol Optimization loop Caching lifecycle Bring your traces (OTLP) Observe a pipeline Migration playbook (T0–T2) Recognition playbooks Tenants & statements Published receipts Provenance & trust Artifact content custody The MCP oracle Use-case templates Template: ticket triage Template: document extraction Template: CS next action Template: lead qualification Template: compliance review Template: catalog cleansing Template: call disposition QA Template: financial classification Template: medical classification Leaderboard

The ModelRig grade protocol (open)

A grade is how judgment enters the optimization loop: a score in [0, 1] — with an optional written comment — that you attach to a subject (one model call, a whole run, or an artifact). The judgment can come from anywhere: your own code, a human, or an AI. ModelRig aggregates grades into per-route optimization coverage — the evidence level that decides how much confidence a swap proposal carries. The protocol is deliberately open: any tool that emits rows in this shape can participate in the loop.

The score is the one primitive. The pass/fail verdict is derived, never storedpass = score ≥ the route's threshold (default 0.5; see the derived verdict). A thumbs-up is just a score of 1, a thumbs-down a 0; a rubric grade like "88 / 100" rides the optional display scale. There is no scoreless judgment.

Every grade is org-scoped — it lands in the organization its credential names — and a grade attached to a run renders on that run's page in the console (see on the run page).

The subject

A grade names exactly one subject by kind and id:

kindidfrom
inferencean inference idresult.meta.inferenceId on a rig.run / rig.runRaw result
runan episode keythe run_id you tagged the run with
artifactan artifact uuidthe ref returned by artifact.save

Attribution is never guessed: a grade whose subject matches no known inference, run, or artifact attaches to nothing.

The call


// one call — the inference id rides on every RunResult:
const result = await rig.run("example.support_summarize", { input, tags: { run_id } });
rig.grade({ kind: "inference", id: result.meta.inferenceId }, { score: 1, kind: "human" });

// a whole run (the subject id is your run_id tag):
rig.grade(
  { kind: "run", id: run_id },
  { score: 0.2, comment: "CAGR off by 10x", kind: "human", source: "review-agent" }
);

The write is synchronous and local (SQLite) — never a network call, never a failure your path can see. The cloud mirror rides the telemetry exporter's normal cadence. To post a grade from a surface that isn't running the SDK — an end user clicking thumbs-down in your web app — use the HTTP endpoint below.

The grade


rig.grade(subject, {
  score: 0.88,                    // 0–1 PRIMITIVE (always present)
  scale: { max: 100 },            // optional display only — { max } or { label: "B+" }
  comment: "Weigh the pending competition next time.",   // written guidance
  kind: "human",                  // "human" | "model-judge" | "deterministic"
  grader: "gpt-5-judge",          // optional specific grader name
  source: "review-agent",         // provenance (sdk, console, an agent name…)
});
fieldtypemeaning
scorenumberthe primitive — a grade in [0, 1], always present
scale{ max } \{ label } \absentdisplay only — never changes the derived verdict, which is a threshold on score
commentstring \nullwritten guidance, saved for a later improvement path
kindhuman \model-judge \deterministicwho — or what — did the judging
graderstring \nullan optional specific grader name (gpt-5-judge, schema-check)
sourcestring \nullprovenance: sdk, console, a review-agent name, …

The three grader kinds

A grade's kind records what did the judging, and the three are kept apart on purpose:

The derived verdict (per route)

The pass/fail verdict is computed at read time, never written: pass = score ≥ routeThreshold(subject's route). Each route may set its own threshold in its grade policy (grade.pass_threshold, 0–1, default 0.5); a subject with no route — a raw-lane call — uses 0.5. Coverage, insights, and the console's pass badge all derive the verdict from the one stored score, so they never disagree.

Native run grades

A single model call either conformed to its schema or it didn't; a whole run — a pipeline of steps and the artifacts they produced — needs a grade of its own. ModelRig writes two kinds automatically, and keeps them honest by keeping them apart: one is computed, free, and automatic; the other calls a model, costs money, and never fires on its own.

run-outcome@v1 — deterministic, automatic

When a run ends, ModelRig writes one run-outcome@v1 grade with no model call — a deterministic grade on the run subject. It scores 1.0 (a pass) iff all three hold:

1. the run succeeded (a running, failed, or abandoned run never passes); 2. no step's latest attempt carries a failure_class; 3. every deterministic grade in the run passed.

Otherwise it scores 0.0, and the grade's display label names the dominant failure — the most frequent step failure class, else the run's non-succeeded status, else failed-evaluation. Same inputs, same grade, every time: it is a pure function over columns the run graph already carries, so it costs nothing and never disagrees with itself. This is the measured floor — a grade you have before any judge is involved.

run-judge@v1 — a model judge, explicit and bounded

Some questions ("did this run actually achieve the outcome?") a deterministic check can't answer. For those there is one built-in model judge, run-judge@v1: it reads the run's step/artifact summary (metadata, declared schemas, and the deterministic grades; content only where custody grants it) and scores outcome-achievement 0–1 with a comment, written as a model-judge grade.

It is deliberately fenced in:


modelrig judge run-123      # score one run with the bounded judge; explicit + metered

Attach your own

Your own code attaches a run-level (or artifact-level) grade the same way as any other — rig.grade with the subject kind:


rig.grade(
  { kind: "run", id: run_id },
  {
    score: 0.9,
    comment: "all figures tied out",
    kind: "deterministic",   // "deterministic" | "model-judge" | "human"
    grader: "my-checker",
    scale: { label: "on-time" },   // optional display label
  }
);

Grades are often post-hoc, so this works even after the run has ended. With the artifact namespace off it is an inert no-op, never an error on your path.

From a non-SDK surface (HTTP)

When the judgment originates somewhere the SDK doesn't run — an end user in your web app, a review tool written in another language — post it to POST /v1/grade on modelrig-server:


curl -X POST https://api.modelrig.ai/v1/grade \
  -H "Authorization: Bearer $MODELRIG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"episode": "run-123", "score": 0.2, "comment": "CAGR off by 10x", "source": "app-user"}'

The ladder (rungs 0–1 today)

Coverage renders per route on the console's /coverage page.

On the run page

Open a run in the console and its grades render as a chip: the deterministic run-outcome@v1 (its derived ✓/✗ plus the failure label) always, and the model judge's score and comment when one has been run. The same page lists the grades attached to the run — end-user thumbs joined by the run's episode, or by one of its steps' inferences when there is no episode — each shown as its score with the route-derived pass badge.

The improvement path

A grade is not only a gate — the comment is written guidance saved for a next run. A grade of 0.2 with "CAGR off by 10x" is the raw material for coaching the model that produced it: the score says how far off, the comment says how. Turning a route's low-scoring grades and their comments into the next attempt's context is the loop's natural next beat — the reason the comment field exists.

Privacy

Grade rows carry your score and comment — never the model's input or output text (those live only in the capture store, and only where a route opts in and your content custody posture allows it). The mirror is subject to the same export isolation as all telemetry.